
//
// 120316 - AH - (Aura) Created.
// 120727a- AH - (Midtun) Object to serialize is now SoekratResponse.
// 120727b- AH - (Midtun) Now generate some json myself because have to handle
//               ParametersSerializable object special.
//

package org.soekrat.server.serialization.json;

import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Array;

import org.soekrat.server.serialization.ParameterSerializable;
import org.soekrat.server.serialization.ParametersSerializable;
import org.soekrat.server.serialization.TextSerializator;
import org.soekrat.server.servlets.SoekratResponse;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

/**
 * <p>
 *   {@link TextSerializator} that serializes to JSON.
 * </p>
 * <p>
 *   See
 *   <a href='http://www.json.org/'>www.json.org</a> for information on this
 *   format.
 * </p>
 * @author Arne Halvorsen (AH)
 * @author Laetitia Le Chatton (LLC)
 */
public final class JsonTextSerializator extends TextSerializator 
{
  private final Gson m_gson;
  
  /**
   * <p>
   *   Constructor.
   * </p>
   */
  public JsonTextSerializator()
  { 
    super("application/json"); 
  
    m_gson=new GsonBuilder().setPrettyPrinting().create();
  }

  @Override
  public String serialize(SoekratResponse sr) throws IOException 
  {
    if (sr==null)
    {
      throw new NullPointerException("o");
    }
    
    CharArrayWriter caw=new CharArrayWriter();
    PrintWriter pw=new PrintWriter(caw);
    
    StringBuilder sb=new StringBuilder();
    
    pw.println("{");
    
    pw.print("  "+par2Json("status", sr.status(), sb));
    
    Object c=sr.response();
    if (c!=null)
    {
      Unpacked unpacked=unpack(c);
      int n=unpacked.objects.length;
      if (n>0)
      {
        pw.println(",");
        sb.setLength(0);
        sb.append('"').append("response").append('"').append(": ");
        pw.println(sb.toString());
        if (n==1)
        {
          if (unpacked.array) pw.println("["); 
          Object e=unpacked.objects[0];
          pw.println(object2Json(e, sb));
          if (unpacked.array) pw.println("]");
        }
        else
        {
          pw.println("[");
          for (int i=0; i<n; i++)
          {
            if (i>0) pw.println(",");
            Object e=unpacked.objects[i];
            pw.println(object2Json(e, sb));
          }
          pw.println("]");
        }
      }
    }
    
    pw.println("}");
    
    pw.flush();
    return caw.toString(); 
  }
  
  private String object2Json(Object o, StringBuilder sb)
  {
    if (o instanceof ParametersSerializable)
    {
      ParametersSerializable ps=(ParametersSerializable)o;
      
      CharArrayWriter caw=new CharArrayWriter();
      PrintWriter pw=new PrintWriter(caw);
      
      int n=ps.getParameterCount();
      
      if (n>0)
      {
        pw.println("{");
        for (int i=0; i<n; i++)
        {
          if (i>0) pw.println(",");
          ParameterSerializable curr=ps.parameter(i);
          pw.print(par2Json(curr.name(), curr.value(), sb));
        }
        pw.println("}");
      }
      
      pw.flush();
      return caw.toString();
    }
    else
    {
      return m_gson.toJson(o);
    }
  }
  
  private String par2Json(String name, String val, StringBuilder sb)
  {
    sb.setLength(0);
    sb.append('"').append(name).append('"').append(": ").append('"')
      .append(val).append('"');
    return sb.toString();
  }
  
  private static final class Unpacked
  {
    boolean array=false;
    
    Object[] objects=E_OBJECT_A;
  }
  
  /**
   * <p>
   *   'Unpacks' given
   *   {@link Object}: If object is an array return array else returns array 
   *   with a single object, the given object. 
   * </p>
   * @param o {@link Object} to 'unpack'.
   * @return Array of 'unpacked' objects.
   */
  private static Unpacked unpack(Object o)
  {
    Unpacked unpacked=new Unpacked();
    if (o.getClass().isArray())
    {
      unpacked.array=true;
      int n=Array.getLength(o);
      if (n==0) return unpacked;
      unpacked.objects=new Object[n];
      for (int i=0; i<n; i++) unpacked.objects[i]=Array.get(o, i);
      return unpacked;
    }
    unpacked.objects=new Object[]{ o };
    return unpacked;
  }
  
  /**
   * <p>
   *   Empty Object array shared.
   * </p>
   */
  private final static Object[] E_OBJECT_A=new Object[0];
  
}
